﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;

namespace SkinableFormsLib
{
	public class LWSkinableForm : Form
	{

		#region Constructors
		#region LWSkinableForm()
		public LWSkinableForm()
		{
			// This form should not have a border or else Windows will clip it.
			FormBorderStyle = FormBorderStyle.None;
			InitializeComponent();
			MakeBlendStruct();

			//TODO fix Dirty hack to override bug in paint:
			Button b = new Button();
			b.Visible = false;
			this.Controls.Add(b);

			this.ControlAdded += new ControlEventHandler(LWSkinableForm_ControlAdded);
			this.ControlRemoved += new ControlEventHandler(LWSkinableForm_ControlRemoved);
			ThreadStart ts = new ThreadStart(WorkingThreadMethod);
			_updateDelegate = new UpdateDelegate(UpdateBitmapThreadHandler);
			_workingThread = new Thread(ts);
		}
		#endregion
		#endregion
		

		#region Delegates
		#region UpdateDelegate()
		public delegate void UpdateDelegate();
		#endregion
		#endregion

		#region Variables

		#region Private
		private Thread _workingThread;
		private Point _mouseDownPoint;
		private bool _mouseDownFlag = false;
		private UpdateDelegate _updateDelegate;
		#endregion

		#region Internal
		internal Bitmap _lwBitmap;
		#endregion

		#endregion

		#region Properties

		#region LWDragByControls
		private bool _lwDragByControls = false;
		public bool LWDragByControls
		{
			set
			{
				if (!DesignMode)
				{
					foreach (Control c in Controls)
					{
						if (value)
						{
							if (_lwDragByControls != value)
							{
								AddControlEvents(c);
							}
						}
						else
						{
							if (_lwDragByControls != value)
							{
								RemoveControlEvents(c);
							}
						}
					}
				}
				_lwDragByControls = value;
			}
			get
			{
				return _lwDragByControls;
			}
		}
		#endregion

		#region LWForceRedraw
		private bool _lwForceRedraw = false;
		public bool LWForceRedraw
		{
			set
			{
				_lwForceRedraw = value;
				if (!DesignMode)
				{
					//_timer1.Enabled = _lwForceRedraw;
				}
			}
			get
			{
				return _lwForceRedraw;
			}
		}
		#endregion

		#region LWThreadSleepInterval
		private int _lwThreadSleepInterval;
		private int LWThreadSleepInterval
		{
			get { return _lwThreadSleepInterval; }
			set
			{
				if (_lwThreadSleepInterval != value)
				{
					_lwThreadSleepInterval = value;
					_workingThread.Interrupt();
				}
				else
				{
					_lwThreadSleepInterval = value;
				}
			}
		}
		#endregion

		#region	LWTimerIntervalFast
		/// <summary>
		/// Used only while the form is activated (in focus)
		/// </summary>
		/// <see cref="P:WindowsFormsApplication1.PerPixelAlphaForm.WndProc"/>
		internal int _lwTimerIntervalFast = 10;
		public int LWTimerIntervalFast
		{
			set
			{
				_lwTimerIntervalFast = value;
			}
			get
			{
				return _lwTimerIntervalFast;
			}
		}
		#endregion

		#region	LWTimerIntervalMedium
		/// <summary>
		/// Most used timer during the lifetime of the form
		/// Not used while dragging or while the form is activated
		/// </summary>
		internal int _lwTimerIntervalMedium = 100;
		public int LWTimerIntervalMedium
		{
			set
			{
				_lwTimerIntervalMedium = value;
			}
			get
			{
				return _lwTimerIntervalMedium;
			}
		}

		#endregion

		#region	LWTimerIntervalSlow

		/// <summary>
		/// used only while the form is being dragged
		/// 
		/// </summary>
		/// <see cref="P:WindowsFormsApplication1.PerPixelAlphaForm.WndProc"/>
		internal int _lwTimerIntervalSlow = 10000;
		public int LWTimerIntervalSlow
		{
			set
			{
				_lwTimerIntervalSlow = value;
			}
			get
			{
				return _lwTimerIntervalSlow;
			}
		}

		#endregion

		#region	LWOpacity
		/// <summary>
		/// Overall form opacity (byte: 0->255)
		/// </summary>
		private byte _lwOpacity = 255;
		public Byte LWOpacity
		{
			set
			{
				_lwOpacity = value;
				if (!DesignMode)
				{
					MakeBlendStruct();
					if (_lwBitmap != null)
					{
						SetBitmap(_lwBitmap, _lwOpacity);
					}
				}
			}

			get
			{
				return _lwOpacity;
			}
		}
		#endregion

		#region BackgroundImage
		public override Image BackgroundImage
		{
			get
			{
				return base.BackgroundImage;
			}
			set
			{
				base.BackgroundImage = value;
				this.Size = BackgroundImage.Size;
				_lwBitmap = null;
			}
		}

		#endregion

		#region Overrides
		#region CreateParams
		protected override CreateParams CreateParams
		{
			get
			{
				CreateParams cp = base.CreateParams;
				if (!this.DesignMode)//!important! - will crash designer in VS 2008 (maybe others too) if not used
				{
					cp.ExStyle |= 0x00080000; // This form has to have the WS_EX_LAYERED extended style
				}
				return cp;
			}
		}
		#endregion
		#endregion

		#endregion

		#region Methods

		#region Overrides

		#region Dispose(bool disposing)
		protected override void Dispose(bool disposing)
		{
			base.Dispose(disposing);
			if (disposing)
			{
				_workingThread.Abort();
			}

		}
		#endregion

		#region WndProc(ref Message m)
		/// <summary>
		/// Overrides WndProc to allow visual dragging
		/// </summary>
		/// <param name="m"></param>
		protected override void WndProc(ref Message m)
		{

			//mess[m.Msg] = m;
			if (!DesignMode)
			{
				//http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1955240&SiteID=1
				if (m.Msg == Win32.WM_NCMOUSEMOVE)
				{
					LWThreadSleepInterval = _lwTimerIntervalSlow;
				}
				if (m.Msg == Win32.WM_NCLBUTTONDBLCLK)
					return;
				if (m.Msg == Win32.WM_NCLBUTTONUP)
					//LWThreadSleepInterval = _lwTimerIntervalFast;
					return;
				if (m.Msg == Win32.WM_NCHITTEST)
				{
					LWThreadSleepInterval = _lwTimerIntervalSlow;
					m.Result = (IntPtr) 2;	// HTCLIENT
					return;
				}
			}
			base.WndProc(ref m);

		}
		#endregion

		#region OnControlAdded(ControlEventArgs e)
		protected override void OnControlAdded(ControlEventArgs e)
		{
			base.OnControlAdded(e);
			if (_lwDragByControls)
			{
				e.Control.MouseDown += new MouseEventHandler(Control_Draggable_MouseDown);
				e.Control.MouseUp += new MouseEventHandler(Control_Draggable_MouseUp);
				e.Control.MouseMove += new MouseEventHandler(Control_Draggable_MouseMove);
			}
			else
			{
				e.Control.MouseDown += new MouseEventHandler(Control_MouseDown);
				e.Control.MouseUp += new MouseEventHandler(Control_MouseUp);
			}
		}
		#endregion

		#region OnControlRemoved(ControlEventArgs e)

		protected override void OnControlRemoved(ControlEventArgs e)
		{
			base.OnControlRemoved(e);
			if (_lwDragByControls)
			{
				try
				{
					e.Control.MouseDown -= new MouseEventHandler(Control_Draggable_MouseDown);
					e.Control.MouseUp -= new MouseEventHandler(Control_Draggable_MouseUp);
					e.Control.MouseMove -= new MouseEventHandler(Control_Draggable_MouseMove);
					e.Control.MouseDown -= new MouseEventHandler(Control_MouseDown);
					e.Control.MouseUp -= new MouseEventHandler(Control_MouseUp);

				}
				catch (Exception) { }
				finally
				{

				}
			}
		}
		#endregion

		#endregion

		#region Private

		#region UpdateBitmapThreadHandler()
		private void UpdateBitmapThreadHandler()
		{
			UpdateBitmap(null, null);
		}
		#endregion

		#region SetBitmap()
		/// <para>Changes the current bitmap.</para>
		public void SetBitmap()
		{
			SetBitmap(_lwBitmap, _lwOpacity);
		}
		#endregion

		#region SetBitmap(Bitmap bitmap)
		/// <para>Changes the current bitmap.</para>
		public void SetBitmap(Bitmap bitmap)
		{
			SetBitmap(bitmap, _lwOpacity);
		}

		#endregion

		#region SetBitmap(Bitmap bitmap, bool change)
		/// <para>Changes the current bitmap.</para>
		public void SetBitmap(Bitmap bitmap, bool change)
		{
			if (change && _lwBitmap != null)
				_lwBitmap.Dispose();
			SetBitmap(bitmap, _lwOpacity);
		}
		#endregion

		#region SetBitmap(Bitmap bitmap, byte opacity)
		/// <para>Changes the current bitmap with a custom opacity level.  Here is where all happens!</para>
		public void SetBitmap(Bitmap bitmap, byte opacity)
		{
			if (DesignMode)
				return;
			if (bitmap == null)
				return;
			if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
				return;

			//_lwBitmap = (Bitmap)bitmap.Clone();
			_lwOpacity = opacity;

			// The idea of this is very simple,
			// 1. Create a compatible DC with screen;
			// 2. Select the bitmap with 32bpp with alpha-channel in the compatible DC;
			// 3. Call the UpdateLayeredWindow.

			IntPtr screenDc = Win32.GetDC(IntPtr.Zero);
			IntPtr memDc = Win32.CreateCompatibleDC(screenDc);
			IntPtr hBitmap = IntPtr.Zero;
			IntPtr oldBitmap = IntPtr.Zero;

			Bitmap temp_bmp = new Bitmap(bitmap);
			Graphics g = Graphics.FromImage(temp_bmp);

			try
			{

				/***********************************/
				/**********  Draw controls *********/
				/***********************************/

				//Bitmap b = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb);

				if (HasChildren)
				{
					this.Refresh();//needed for controls!	  
				}
				foreach (Control ctrl in this.Controls)
				{
					
					if (ctrl.Visible)
					{
						if (ctrl is ILWControl)
						{
							//ctrl.DrawToBitmap(new Bitmap(1, 1), ctrl.Bounds);//bad idea
							//Win32.PostMessageSafe(new System.Runtime.InteropServices.HandleRef(ctrl, ctrl.Handle), Win32.WM_PAINT, IntPtr.Zero, IntPtr.Zero);//bad idea

							if (((ILWControl) ctrl).LWBitmap != null)
							{
								g.DrawImage(((ILWControl) ctrl).LWBitmap, ctrl.Location);
							}
						}
						else
						{
							ctrl.DrawToBitmap(temp_bmp, ctrl.Bounds);
						}
					}
				}

				//g.Flush();

				hBitmap = temp_bmp.GetHbitmap(Color.FromArgb(0));
				//hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));  // grab a GDI handle from this GDI+ bitmap
				oldBitmap = Win32.SelectObject(memDc, hBitmap);

				Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height);
				Win32.Point pointSource = new Win32.Point(0, 0);
				Win32.Point topPos = new Win32.Point(Left, Top);
				this.Location = new Point(topPos.x, topPos.y);
				this.Size = new Size(size.cx, size.cy);

				   /*
				Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION();
				blend.BlendOp = Win32.AC_SRC_OVER;
				blend.BlendFlags = 0;
				blend.SourceConstantAlpha = opacity;
				blend.AlphaFormat = Win32.AC_SRC_ALPHA;
				    * */

				
				Win32.UpdateLayeredWindow(Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref _blend, Win32.ULW_ALPHA);

				//Win32.PostMessageSafe(new System.Runtime.InteropServices.HandleRef(this,Handle), Win32.wm_a, memDc, IntPtr.Zero);


			}
			catch (Exception ex)
			{
				Console.WriteLine(ex);
			}
			finally
			{
				g.Dispose();
				temp_bmp.Dispose();
				Win32.ReleaseDC(IntPtr.Zero, screenDc);
				if (hBitmap != IntPtr.Zero)
				{
					Win32.SelectObject(memDc, oldBitmap);
					//Windows.DeleteObject(hBitmap); // The documentation says that we have to use the Windows.DeleteObject... but since there is no such method I use the normal DeleteObject from Win32 GDI and it's working fine without any resource leak.
					Win32.DeleteObject(hBitmap);
				}
				Win32.DeleteDC(memDc);
				//bitmap.Dispose();


			}
		}
		#endregion

		Win32.BLENDFUNCTION _blend;
		#region MakeBlendStruct()
		private void MakeBlendStruct()
		{
			_blend = new Win32.BLENDFUNCTION();
			_blend.BlendOp = Win32.AC_SRC_OVER;
			_blend.BlendFlags = 0;
			_blend.SourceConstantAlpha = _lwOpacity;
			_blend.AlphaFormat = Win32.AC_SRC_ALPHA;

		}
		#endregion







		#region InitializeComponent()
		private void InitializeComponent()
		{

			this.SuspendLayout();


			// 
			// PerPixelAlphaForm
			// 
			this.ClientSize = new System.Drawing.Size(289, 273);
			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
			this.Name = "PerPixelAlphaForm";
			this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
			this.Deactivate += new System.EventHandler(this.PerPixelAlphaForm_Deactivate);
			this.Load += new System.EventHandler(this.PerPixelAlphaForm_Load);
			this.Activated += new System.EventHandler(this.PerPixelAlphaForm_Activated);
			this.ResumeLayout(false);
		}
		#endregion

		#region UpdateBitmap(object sender, EventArgs e)
		public virtual void UpdateBitmap(object sender, EventArgs e)
		{

			//Console.WriteLine("tick");			return;
			if (_lwBitmap == null)
			{
				Console.WriteLine("XXX");
				_lwBitmap = (Bitmap) BackgroundImage;
			}
			SetBitmap(_lwBitmap, _lwOpacity);
		}
		#endregion

		#region AddControlEvents(Control c)
		private void AddControlEvents(Control c)
		{
			c.MouseDown += new MouseEventHandler(Control_Draggable_MouseDown);
			c.MouseUp += new MouseEventHandler(Control_Draggable_MouseUp);
			c.MouseMove += new MouseEventHandler(Control_Draggable_MouseMove);
		}

		void c_Invalidated(object sender, InvalidateEventArgs e)
		{
			LWThreadSleepInterval = _lwTimerIntervalFast;
		}
		#endregion

		#region RemoveControlEvents(Control c)
		private void RemoveControlEvents(Control c)
		{
			c.MouseDown -= new MouseEventHandler(Control_Draggable_MouseDown);
			c.MouseUp -= new MouseEventHandler(Control_Draggable_MouseUp);
			c.MouseMove -= new MouseEventHandler(Control_Draggable_MouseMove);
		}
		#endregion

		#endregion

		#region Internal

		#region WorkingThreadMethod()
		internal virtual void WorkingThreadMethod()
		{
			while (true)
			{
				if (!this.Created)
				{
					return;
				}

				IAsyncResult ias = BeginInvoke((_updateDelegate));
				try
				{
					Thread.Sleep(_lwThreadSleepInterval);
				}
				catch (ThreadInterruptedException)
				{
					if (this.Created)
					{
						EndInvoke(ias);
					}
				}
				finally { }

				while (!ias.IsCompleted)
				{
				}

				if (this.Created)
				{
					EndInvoke(ias);
				}

			}


		}
		#endregion

		#endregion

		#endregion

		#region EventHandlers

		#region PerPixelAlphaForm_Load(object sender, EventArgs e)
		private void PerPixelAlphaForm_Load(object sender, EventArgs e)
		{
			if (!DesignMode)
			{
				if (_workingThread.ThreadState==ThreadState.Unstarted)
					_workingThread.Start();
			}
			this.Controls.RemoveAt(0);
		}
		#endregion

		#region PerPixelAlphaForm_Activated(object sender, EventArgs e)
		private void PerPixelAlphaForm_Activated(object sender, EventArgs e)
		{
			LWThreadSleepInterval = _lwTimerIntervalMedium;
		}
		#endregion

		#region PerPixelAlphaForm_Deactivate(object sender, EventArgs e)
		private void PerPixelAlphaForm_Deactivate(object sender, EventArgs e)
		{
			LWThreadSleepInterval = _lwTimerIntervalMedium;
		}

		#endregion

		#region Control_MouseUp(object sender, MouseEventArgs e)
		private void Control_MouseUp(object sender, MouseEventArgs e)
		{
			LWThreadSleepInterval = _lwTimerIntervalMedium;
		}

		#endregion

		#region Control_MouseDown(object sender, MouseEventArgs e)
		void Control_MouseDown(object sender, MouseEventArgs e)
		{
			LWThreadSleepInterval = _lwTimerIntervalFast;
		}

		#endregion

		#region Control_Draggable_MouseMove(object sender, MouseEventArgs e)
		private void Control_Draggable_MouseMove(object sender, MouseEventArgs e)
		{

			if (_mouseDownFlag)
			{
				Point nl = Location;
				nl.Offset(e.X - _mouseDownPoint.X, e.Y - _mouseDownPoint.Y);
				this.Location = nl;
			}
		}

		#endregion

		#region Control_Draggable_MouseUp(object sender, MouseEventArgs e)
		private void Control_Draggable_MouseUp(object sender, MouseEventArgs e)
		{
			_mouseDownFlag = false;
			LWThreadSleepInterval = _lwTimerIntervalFast;
		}

		#endregion

		#region	Control_Draggable_MouseDown(object sender, MouseEventArgs e)
		private void Control_Draggable_MouseDown(object sender, MouseEventArgs e)
		{
			LWThreadSleepInterval = _lwTimerIntervalSlow;
			_mouseDownFlag = true;
			_mouseDownPoint = e.Location;
		}

		#endregion

		#region LWSkinableForm_ControlRemoved(object sender, ControlEventArgs e)
		private void LWSkinableForm_ControlRemoved(object sender, ControlEventArgs e)
		{
			if (e.Control is ILWControl)
			{
				if (((ILWControl) e.Control).LWDragForm)
				{
					RemoveControlEvents(e.Control);
				}
			}
		}
		#endregion

		#region LWSkinableForm_ControlAdded(object sender, ControlEventArgs e)
		private void LWSkinableForm_ControlAdded(object sender, ControlEventArgs e)
		{
			if (e.Control is ILWControl)
			{
				if (((ILWControl) e.Control).LWDragForm)
				{
					AddControlEvents(e.Control);
				}
			}

		}
		#endregion



		#endregion

		#region Comments, useless stuff
		//IDictionary<int,Message> mess=new Dictionary<int,Message>();




		/*
		public void exportMess()
		{
			StreamWriter sw= File.CreateText(DateTime.Now.Ticks.ToString() + ".txt");
			foreach (KeyValuePair<int, Message> m in mess)
			{
				sw.WriteLine(m.Key);
				
			}

			sw.Close();
		}

		protected override void OnClosed(EventArgs e)
		{
			exportMess();
			base.OnClosed(e);
		}
		*/
		#endregion

	}
}
